package SerialLibs::LG;
$SerialLibs::LG::VERSION = '0.11';

################################################################################
#
#	Heavily based upon RFLibs::Aquos which was:
#       Created by Ronald Frazier
#       http://www.ronfrazier.net
#
#	Modified by Wayne Thomas to Control an LG Television as opposed
#	to a Sharp Television
# 
#       Feel free to do anything you want with this, as long as you
#       include the above attribution.
#
#	Todo:	Try newinput before old input (makes sense)
#		Minor Tidy Ups
#
################################################################################

use strict;
use Device::SerialPort;
use Time::HiRes qw(sleep);

my %debuglevels = ('NONE' => 0, 'ERROR' => 1 , 'WARN' => 2, 'INFO' => 3);
my @inputs = ('DTV', 'ATV', 'AV1', 'AV2', 'COMP1', 'COMP2', 'PC', 'HDMI1', 'HDMI2', 'OSDMUTE');
my %input_to_code = (	'DTV' => 0, 'ATV' => 1, 'AV1' => 2, 'AV2' => 3, 'COMP1' => 4, 'COMP2' => 5,
			'PC' => 6, 'HDMI1' => 7, 'HDMI2' => 8, 'OSDMUTE' => 9);
my %newinput_to_code = ('DTV' => 0, 'OSDMUTE' => 9, 'ATV' => 10, 'AV1' => 20,'AV2' => 21,'AV3' => 22, 'COMP1' => 40,
			'PC' => 60, 'HDMI1' => 90, 'HDMI2' => 91, 'HDMI3' => 92, 'HDMI4' => 93);
my %code_to_newinput = (0 => 'DTV', 10 => 'ATV', 20 => 'AV1', 21 => 'AV2', 22 => 'AV3', 40 => 'COMP1',
			60 => 'PC', 90 => 'HDMI1', 91 => 'HDMI2', 92 => 'HDMI3', 93 => 'HDMI4');
			

sub new
{
        my ($classtype, $serialport_device, $volume) = @_;

        my $self = {};
        bless $self, $classtype;

	$self->{volume} = 0;
	$self->{volume} = 1 if ($volume eq 'yes');
        return $self->init($serialport_device);
};

sub init
{
	my ($self, $serialport_device) = @_;

	my $port = Device::SerialPort->new($serialport_device) or return 0;
	$port->baudrate(9600);
	$port->parity("none");
	$port->databits(8);
	$port->stopbits(1); 
	$port->handshake("none");
	$self->{port} = $port;

	$self->debug('WARN');

	return $self;
};

sub kill
{
        my ($self) = @_;

        return $self->{port}->close;
};

sub debug
{
	my ($self, $debug) = @_;
	$self->{debug} = $debug if defined($debug);
	return $self->{debug};
};

sub debugprint
{
	my ($self, $level, @msg) = @_;
	return if $debuglevels{$self->{debug}} < $debuglevels{$level};
	print STDERR @msg;
};

sub power
{
	my ($self, $value) = @_;
	my $pause = 0;
	if ($value eq '1')
	{
		my $result = $self->get('ka 1 ');
		$pause = 5 unless (substr($result, 7, 2) eq '01');
	}
	my $result = $self->getset('ka 1 ', $value);
	return $result if $result eq 'ERR';
	$result = substr($result, 7, 2);
	$result = ($result eq '01' ? 'ON' : 'OFF');
	sleep $pause;
	return "TV $result";
};

sub volume
{
	my ($self, $value) = @_;
	return 'NOP' unless $self->{volume};
        $value = 0 if ($value < 0);
	$value = 100 if ($value > 100);
	$value = sprintf("%02x", $value) unless $value eq '';
	my $result = $self->getset('kf 1 ', $value);
	return 'ERR' if $result eq 'ERR';
	$result = substr($result, 7, 2);
	$result = hex($result);
	return "VOLUME " . $result;
};

sub adjust_volume
{
	my ($self, $adjust) = @_;
	return 'NOP' unless $self->{volume};
	my $current = $self->get('kf 1 ');
	return 'ERR' if $current eq 'ERR';
        $current = substr($current, 7, 2);
        $current = hex($current);
	my $new = $current + ($adjust*2);
	$new = 0 if ($new < 0);
	$new = 100 if ($new > 100);
        $new = sprintf("%02x", $new);
	$current = $self->set('kf 1 ', $new);
	return 'ERR' if $current eq 'ERR';
        $current = substr($current, 7, 2);
        $current = hex($current);
	return "VOLUME " . $current;
};

sub mute
{
        my ($self, $value) = @_;
	return 'NOP' unless $self->{volume};
	my $result;
        if ($value eq '') {
                $result =  $self->get('ke 1 ');
        }
        else {
                $result =  $self->getset('ke 1 ', optbool($value,0,1));
        }
        return 'ERR' if $result eq 'ERR';
        $result = substr($result, 7, 2);
        $result = ($result eq '00' ? 'ON' : 'OFF');
        return "MUTE $result";
};

sub toggle_mute
{
	my ($self) = @_;
	return 'NOP' unless $self->{volume};
	my $current =  $self->get('ke 1 ');
	return 'ERR' if $current eq 'ERR';
	my $new = ($current eq 'e 01 OK01' ? 00 : 01);
	my $result = $self->set('ke 1 ', $new);
	$result = substr($result, 7, 2);
	$result = ($result eq '00' ? 'ON' : 'OFF');
	return "MUTE $result";
};

sub input
{
        my ($self, $value) = @_;
        my $code;
        my $result = $self->get('kb 1 ');
        if ($value ne '') {
		return 'NOP' if (!defined ($input_to_code{$value}));
		$code = $input_to_code{$value};
#                return 'NOP' if $code == -1;
        	if ($code == 9) {
                	$result = $self->set('kl 1 ', '0');
                	return "LG OSD MUTED" unless $result eq 'ERR';
                	return $result;
        	}
	};
        return $self->new_input($value) if $result eq 'ERR';
        $result = $self->set('kb 1 ', $code) unless $value eq '';
        return $result if $result eq 'ERR';
        $result = substr($result, 8, 1);
        $result = $inputs[$result];
        return "TV_IN $result";
};

sub new_input
{
        my ($self, $value) = @_;
	my $code;
	my $result;
        $result = $self->get('xb 1 ') if $value eq '';
        if ($value ne '') {
		$code = $newinput_to_code{$value};
        	return 'NOP' if $code == -1;
	};
	$result = $self->set('xb 1 ', $code) unless $value eq '';
	return $result if $result eq 'ERR';
	$result = substr($result, 7, 2);
	$result = $code_to_newinput{$result};
        return "TV_IN $result";
};

sub optbool
{
	my ($value, $true, $false) = @_;
	return unless defined($value);
	return $value ? $true : $false;
};

sub get
{
	my ($self, $cmd) = @_;
	return $self->sendCommand($cmd.'ff');
};

sub set
{
	my ($self, $cmd, $value) = @_;
        my $index = $self->sendCommand("$cmd$value");
        $self->debugprint('INFO', "LG sent $cmd$value, received $index as reply\n");
	return $index;
};

sub getset
{
	my ($self, $cmd, $value) = @_;
	return $self->get($cmd) unless $value ne '';
	return $self->set($cmd, $value);
};


sub sendCommand
{
	my ($self, $cmd) = @_;
	my $result;
	foreach (1..3)
	{
		$result = $self->sendCommand_inner($cmd);
		$self->debugprint('INFO', "LGSend Command Result: $result\n");
		$self->debugprint('INFO', "LG Bad Response: $result\n") if $result =~ /NG/;
		next if $result =~ /NG/;
                $self->debugprint('INFO', "LG No Response\n") if $result eq 'NORESPONSE';
		next if $result eq 'NORESPONSE';

		return $result;
	};
	$self->debugprint('INFO', "LG Five Failed Send Attempts\n");
	return 'ERR';
};

sub sendCommand_inner
{
	my ($self, $cmd) = @_;
	$self->debugprint('INFO', "LG\t\t$cmd\n");

	#clear the buffer
	while(1)
	{
		my ($bytes, $read) = $self->{port}->read(1);
		last if $bytes == 0;
		$self->debugprint('INFO', "LG $bytes byte(s) -->$read<-- cleared from buffer\n");
	};


	$self->{port}->write("$cmd\r");

	my $data = '';
	my $start = Time::HiRes::time();
	while(1)
	{
		if ((Time::HiRes::time() - $start) >= 2)
		{
			$self->debugprint('INFO', "LG read timeout with partial data: -->$data<--\n") if ($data ne '');
			$self->debugprint('INFO', "LG read timeout with no response\n") if ($data eq '');
			return 'NORESPONSE';
		};

		my ($bytes, $read) = $self->{port}->read(1);
		next if $bytes == 0;
		return $data if ($read eq "\n");
		$data .= $read;
	};	
};

return 1;
